﻿#include "mainwindow.h"
#include <QWKWidgets/widgetwindowagent.h>
#include <QLabel>
#include <WindowBar.h>
#include <WindowButton.h>
#include <QTimer>
#include <QFile>
#include <QStyle>
#include <QDir>
#include <QCefSetting.h>
#include <QCefView.h>
#include <ShellAudio.h>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonObject>
#include <QMetaMethod>
#include <QGenericArgument>
#include <QException>
#include <QThread>
#include <QCefContext.h>
#include "ConfigDef.h"

static inline void emulateLeaveEvent(QWidget *widget) {
    Q_ASSERT(widget);
    if (!widget) {
        return;
    }
    QTimer::singleShot(0, widget, [widget]() {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
        const QScreen *screen = widget->screen();
#else
            const QScreen *screen = widget->windowHandle()->screen();
#endif
        const QPoint globalPos = QCursor::pos(screen);
        if (!QRect(widget->mapToGlobal(QPoint{0, 0}), widget->size()).contains(globalPos)) {
            QCoreApplication::postEvent(widget, new QEvent(QEvent::Leave));
            if (widget->testAttribute(Qt::WA_Hover)) {
                const QPoint localPos = widget->mapFromGlobal(globalPos);
                const QPoint scenePos = widget->window()->mapFromGlobal(globalPos);
                static constexpr const auto oldPos = QPoint{};
                const Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
                const auto event =
                    new QHoverEvent(QEvent::HoverLeave, scenePos, globalPos, oldPos, modifiers);
                Q_UNUSED(localPos);
#elif (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
                    const auto event =  new QHoverEvent(QEvent::HoverLeave, localPos, globalPos, oldPos, modifiers);
                    Q_UNUSED(scenePos);
#else
                    const auto event =  new QHoverEvent(QEvent::HoverLeave, localPos, oldPos, modifiers);
                    Q_UNUSED(scenePos);
#endif
                QCoreApplication::postEvent(widget, event);
            }
        }
    });
}
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->resize(1230,760);
    this->setWindowTitle(TITLE_TEXT);
    _cefWidget = nullptr;
    _setDlg = new SettingsDialog(this);
    connect(_setDlg,&SettingsDialog::serverChanged,this,&MainWindow::reload);

    installWindowAagent();
    loadStyleSheet(Light);
    initServices();

    initCefView();
    loadCefView();

    // qDebug()<<"main thread"<<QThread::currentThreadId();
}

MainWindow::~MainWindow()
{

}

void MainWindow::initServices()
{
    _queryServices << ShellAudio::getInstance();
    connect(ShellAudio::getInstance(),&ShellAudio::playerStateChanged,this,&MainWindow::notifyPlayerMsg);
}

void MainWindow::installWindowAagent()
{
    // 1. Setup window agent
    windowAgent = new QWK::WidgetWindowAgent(this);
    windowAgent->setup(this);

    auto titleLabel = new QLabel();
    titleLabel->setAlignment(Qt::AlignCenter);
    titleLabel->setObjectName(QStringLiteral("win-title-label"));

    auto iconButton = new QWK::WindowButton();
    iconButton->setObjectName(QStringLiteral("icon-button"));
    iconButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);


    auto settingsButton = new QWK::WindowButton();
    _setBtn = settingsButton;
    settingsButton->setObjectName(QStringLiteral("settings-button"));
    settingsButton->setProperty("system-button",true);
    settingsButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

    auto minButton = new QWK::WindowButton();
    minButton->setObjectName(QStringLiteral("min-button"));
    minButton->setProperty("system-button", true);
    minButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

    auto maxButton = new QWK::WindowButton();
    maxButton->setCheckable(true);
    maxButton->setObjectName(QStringLiteral("max-button"));
    maxButton->setProperty("system-button", true);
    maxButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

    auto closeButton = new QWK::WindowButton();
    closeButton->setObjectName(QStringLiteral("close-button"));
    closeButton->setProperty("system-button", true);
    closeButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

    _windowBar = new QWK::WindowBar();

    _windowBar->setHostWidget(this);

    _windowBar->setTitleLabel(titleLabel);


    _windowBar->setIconButton(iconButton);
    _windowBar->setMinButton(minButton);
    _windowBar->setMaxButton(maxButton);
    _windowBar->setCloseButton(closeButton);

    QSettings settings(CONFIG_FILE,QSettings::IniFormat);
    auto showDev = settings.value("ShowDev",false).toBool();
    if(showDev){
        auto devBtutton = new QWK::WindowButton();
        devBtutton->setObjectName(QStringLiteral("dev-button"));
        devBtutton->setProperty("system-button", true);
        devBtutton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
        _windowBar->setDevButton(devBtutton);
        windowAgent->setHitTestVisible(devBtutton,true);
        connect(_windowBar,&QWK::WindowBar::devRequested,this,&MainWindow::showDev);
    }

    _windowBar->setSettingsButton(settingsButton);
    windowAgent->setHitTestVisible(settingsButton,true);

    windowAgent->setTitleBar(_windowBar);


    windowAgent->setSystemButton(QWK::WindowAgentBase::WindowIcon, iconButton);
    windowAgent->setSystemButton(QWK::WindowAgentBase::Minimize, minButton);
    windowAgent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton);
    windowAgent->setSystemButton(QWK::WindowAgentBase::Close, closeButton);
    _windowBar->setMinimumHeight(35);

    setMenuWidget(_windowBar);

    connect(_windowBar, &QWK::WindowBar::minimizeRequested, this, &QWidget::showMinimized);



    connect(_windowBar,&QWK::WindowBar::settingsRequested,this,[this]{
        auto pos = _setBtn->mapToGlobal(QPoint(0,30));
        _setDlg->showPopup(pos);
    });
    connect(_windowBar, &QWK::WindowBar::maximizeRequested, this, [this, maxButton](bool max) {
        if (max) {
            showMaximized();
        } else {
            showNormal();
        }

        // It's a Qt issue that if a QAbstractButton::clicked triggers a window's maximization,
        // the button remains to be hovered until the mouse move. As a result, we need to
        // manually send leave events to the button.
        emulateLeaveEvent(maxButton);
    });
    connect(_windowBar, &QWK::WindowBar::closeRequested, this, &QWidget::close);
}
void MainWindow::initCefView()
{

    // build the path to the web resource
    QDir dir = QCoreApplication::applicationDirPath();
#if defined(Q_OS_MACOS)
    QString webResourceDir = /*QString("file://") +*/ QDir::toNativeSeparators(dir.filePath("../Resources/webres"));
#else
    QString webResourceDir = /*QString("file://") +*/ QDir::toNativeSeparators(dir.filePath("webres"));
#endif
    qDebug()<<webResourceDir;
    // add a local folder to URL map (global)
    QCefContext::instance()->addLocalFolderResource(webResourceDir, URL_ROOT);
    // build settings for per QCefView
    QCefSetting setting;
    // here we just set the default background to blue
    setting.setBackgroundColor(QColor::fromRgb(255, 255, 255));

    // create the QCefView widget and add it to the layout container
    _cefWidget = new QCefView("", &setting, this,Qt::CustomizeWindowHint|Qt::FramelessWindowHint|Qt::SubWindow);
    connect(_cefWidget,&QCefView::invokeMethod,this,&MainWindow::onMethodInvoke);
    connect(_cefWidget,&QCefView::cefQueryRequest,this,&MainWindow::onCefQueryRequest);
    connect(_cefWidget,&QCefView::loadError,this,[](const QCefBrowserId& browserId,
                                                       const QCefFrameId& frameId,bool isMainFrame, int errorCode, const QString & errorMsg, const QString & failedUrl){
        qDebug()<<"loadError"<<browserId<<frameId<<isMainFrame<<errorCode<<errorMsg<<failedUrl;
    });
    connect(_cefWidget,&QCefView::loadEnd,this,[this](const QCefBrowserId& browserId, const QCefFrameId& frameId, bool isMainFrame, int httpStatusCode){
        qDebug()<<"loadEnd"<<browserId<<frameId<<isMainFrame<<httpStatusCode;
        if(httpStatusCode != 200){
            loadCefView(NOT_FOUND_URL);
        }
    });
    connect(_cefWidget,&QCefView::faviconURLChanged,this,[](const QStringList & urls){
        qDebug()<<"faviconURLChanged"<<urls;
    });
    connect(_cefWidget,&QCefView::loadStart,this,[](const QCefBrowserId& browserId, const QCefFrameId& frameId, bool isMainFrame, int transition_type){
        qDebug()<<"loadStart"<<browserId<<frameId<<isMainFrame<<transition_type;
    });
    connect(_cefWidget,&QCefView::loadingStateChanged,this,[](const QCefBrowserId& browserId, bool isLoading, bool canGoBack, bool canGoForward){
        qDebug()<<"loadingStateChanged"<<browserId<<isLoading<<canGoBack<<canGoForward;
    });
    connect(_cefWidget,&QCefView::loadingProgressChanged,this,[](double progress){
        qDebug()<<"loadingProgressChanged"<<progress;
    });
    this->setCentralWidget(_cefWidget);
}
void MainWindow::loadStyleSheet(Theme theme)
{
    if (!styleSheet().isEmpty() && theme == currentTheme)
        return;
    currentTheme = theme;

    if (QFile qss(theme == Dark ? QStringLiteral(":/dark-style.qss")
                                : QStringLiteral(":/light-style.qss"));
        qss.open(QIODevice::ReadOnly | QIODevice::Text)) {
        setStyleSheet(QString::fromUtf8(qss.readAll()));
        // Q_EMIT themeChanged();
    }
}

bool MainWindow::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::WindowActivate: {
        if (_windowBar) {
            _windowBar->setProperty("bar-active", true);
            style()->polish(_windowBar);
        }
        break;
    }

    case QEvent::WindowDeactivate: {
        if (_windowBar) {
            _windowBar->setProperty("bar-active", false);
            style()->polish(_windowBar);
        }
        break;
    }

    default:
        break;
    }
    return QMainWindow::event(event);
}



void MainWindow::onMethodInvoke(const QCefBrowserId& browserId,
                                const QCefFrameId& frameId,
                                const QString& method,
                                const QVariantList& arguments)
{
    qDebug()<<method<<arguments;
    QMetaObject::invokeMethod(
        this,
        [=](const QString method, const QVariantList args) {
            try {
                // auto args = arguments;
                dispatch(method,args);
            } catch (std::runtime_error err) {
                qDebug()<<err.what();
            }
        },Qt::QueuedConnection,method, arguments);
}
void MainWindow::onCefQueryRequest(const QCefBrowserId& browserId, const QCefFrameId& frameId, const QCefQuery& query)
{
    QMetaObject::invokeMethod(
        this,
        [=]() {
            QVariant retValue;
            QString errMsg;
            try {
                QJsonParseError jsonErr;
                QJsonDocument jsonDoc = QJsonDocument::fromJson(query.request().toUtf8(),&jsonErr);
                if(jsonErr.error != QJsonParseError::NoError){
                    throw std::runtime_error(QString("json parse error : %1").arg(jsonErr.errorString()).toStdString());
                }
                auto jsonObject = jsonDoc.object();
                auto methodName = jsonObject.value("method").toString();
                auto params = jsonObject.value("params").toVariant().toMap();
                qDebug()<<methodName<<params;
                dispatch(methodName,params,retValue);
            } catch (std::runtime_error err) {
                errMsg = err.what();
            }
            bool success = false;
            QString response = "";
            int errorCode = 0;
            if(!errMsg.isEmpty()){
                qDebug()<<errMsg;
                success = false;
                response = errMsg;
                errorCode = -1;
            }else{
                success = true;
                auto type = retValue.metaType();
                if(type.id() == QMetaType::QVariantList ||
                    type.id() == QMetaType::QVariantMap ||
                    type.id() == QMetaType::QStringList||
                    type.id()==QMetaType::QVariantHash){

                    response = QString::fromUtf8(QJsonDocument::fromVariant(retValue).toJson(QJsonDocument::Compact));
                }else{
                    response = retValue.toString();
                }
            }
            qDebug()<<response;

            query.setResponseResult(success, response,errorCode);
            _cefWidget->responseQCefQuery(query);
        },
        Qt::QueuedConnection);

}

void MainWindow::dispatch(const QString methodName, QVariantMap &args, QVariant &ret)
{
    if(methodName.isEmpty()){
        throw std::runtime_error("missing method");
    }
    QVariantList arguments;
    for(auto it:_queryServices){
        auto metaObject = it->metaObject();
        for(auto i =0;i<metaObject->methodCount();i++){
            auto metaMethod = metaObject->method(i);
            if(metaMethod.name() == methodName) {
                auto paramNames = metaMethod.parameterNames();
                auto paramTypes = metaMethod.parameterTypes();
                for(int y = 0;y < metaMethod.parameterCount(); y++){
                    auto param = args[paramNames.at(y)];
                    if(!param.isValid()){
                        throw std::runtime_error(QString("missing parameter : %1").arg(paramNames.at(y)).toStdString());
                    }
                    auto type = QMetaType::fromName(paramTypes.at(y));
                    if(param.metaType() != type){
                        if(param.canConvert(type)) {
                            if(!param.convert(type)) {
                                throw std::runtime_error(QString("parameter type err : %1").arg(paramNames.at(y)).toStdString());
                            }
                        }else{
                            throw std::runtime_error(QString("parameter type err : %1").arg(paramNames.at(y)).toStdString());
                        }
                    }
                    arguments<<param;
                }
                doCall(it,metaMethod,arguments,ret);
                return;
            }
        }
    }
    throw std::runtime_error("method not found");
}

void MainWindow::dispatch(const QString methodName,const QVariantList &args)
{
    if(methodName.isEmpty()){
        throw std::runtime_error("missing method");
    }
    QVariantList arguments;
    QVariant ret;
    for(auto it:_queryServices){
        auto metaObject = it->metaObject();
        for(auto i =0;i<metaObject->methodCount();i++){
            auto metaMethod = metaObject->method(i);
            if(metaMethod.name() == methodName) {
                auto paramNames = metaMethod.parameterNames();
                auto paramTypes = metaMethod.parameterTypes();
                for(int y = 0;y < metaMethod.parameterCount(); y++){
                    auto param = args[y];
                    if(!param.isValid()){
                        throw std::runtime_error(QString("missing parameter : %1").arg(paramNames.at(y)).toStdString());
                    }
                    auto type = QMetaType::fromName(paramTypes.at(y));
                    if(param.metaType() != type){
                        if(param.canConvert(type)) {
                            if(!param.convert(type)) {
                                throw std::runtime_error(QString("parameter type err : %1").arg(paramNames.at(y)).toStdString());
                            }
                        }else{
                            throw std::runtime_error(QString("parameter type err : %1").arg(paramNames.at(y)).toStdString());
                        }
                    }
                    arguments<<param;
                }
                doCall(it,metaMethod,arguments,ret);
                return;
            }
        }
    }
    throw std::runtime_error("method not found");
}

void MainWindow::doCall(QObject *object, const QMetaMethod &metaMethod, QVariantList &args, QVariant &retVal)
{
    QList<QGenericArgument> arguments;
    for(auto &it: args) {
        QGenericArgument genericArgument(
            it.typeName(),
            const_cast<void*>(it.constData())
            );
        arguments << genericArgument;
    }
    auto returnTypeName = metaMethod.typeName();
    auto returnType = QMetaType::fromName(returnTypeName);
    if ((returnType.id() != QMetaType::Void) && (returnType.id() != QMetaType::QVariant)) {
        retVal = QVariant(returnType, nullptr);
    }
    QGenericReturnArgument returnArgument(
        metaMethod.typeName(),
        const_cast<void*>(retVal.constData())
        );
    if(!metaMethod.invoke(
        object,
        Qt::DirectConnection,
        returnArgument,
        arguments.value(0),
        arguments.value(1),
        arguments.value(2),
        arguments.value(3),
        arguments.value(4),
        arguments.value(5),
        arguments.value(6),
        arguments.value(7),
        arguments.value(8),
        arguments.value(9)
            )){
        throw std::runtime_error("method invoke error");
    }
}

void MainWindow::reload(QString url)
{
    loadCefView(url);
    ShellAudio::getInstance()->stopAll();
}

void MainWindow::loadCefView(QString url)
{    
    if(url.isEmpty()){
        QSettings config(CONFIG_FILE,QSettings::IniFormat);
        url = config.value("ServerAddress","").toString();
        if(url.isEmpty()){
            url = NOT_FOUND_URL;
        }
    }
    _cefWidget->navigateToUrl(url+URL_PARAMS);
}

void MainWindow::notifyPlayerMsg(MediaPlayer::PLAYER_STATE playerState,QString taskId, int sessionId, QString msg)
{
    if (_cefWidget) {
        QCefEvent event("RecorderStateChanged");
        event.arguments()<<playerState<<taskId<<sessionId<<msg;
        _cefWidget->broadcastEvent(event);
    }
}

void MainWindow::showDev()
{
    if(_cefWidget!=nullptr){
        _cefWidget->showDevTools();
    }
}
